/**
 *  renderer.cpp
 *  AwkwardCubed - Renderer
 *
 *  Date: 20/08/2013
 *
 *  Language: C++
 *
 *  @author Carl mcCaffrey
 *
 */
#include "renderer/renderer.h"

bool wndState;
int wndStateChanged;

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

bool Renderer::Init(RENDERER_DESC desc)
{	
	Debug& dbg = Debug::GetInstance();
	dbg.Print("Initializing Renderer...\n");

	// Load RENDERER_DESC
	dbg.Print("\tLoading RENDERER_DESC...\n");
	hInstance = desc.hInstance;
	wndSize = desc.wndSize;
	wndFullscreen = wndState = desc.wndFullscreen;
	wndCursorVisible = desc.wndCursorVisible;

	SetClearColor(desc.d3dClearColor);
	d3dVSync = desc.d3dVSync;
	d3dMSAASamples = desc.d3dMSAASamples;

	if(wndFullscreen && wndSize.x == 0){
		wndSize.x = static_cast<float>(GetSystemMetrics(SM_CXSCREEN));
		wndSize.y = static_cast<float>(GetSystemMetrics(SM_CYSCREEN));
	}else if(!wndFullscreen && wndSize.x == 0){
		wndSize.x = 800;
		wndSize.y = 600;
	}

	// Create WNDCLASSEX
	dbg.Print("\tCreating WNDCLASSEX...\n");
	WNDCLASSEX wndClass;
	ZeroMemory(&wndClass, sizeof(WNDCLASSEX));
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hInstance = *hInstance;
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.lpszClassName = WINDOW_TITLE;
	wndClass.hIcon = LoadIcon(NULL, IDI_WINLOGO);//(HICON)LoadImage(GetModuleHandle(NULL), WINDOW_ICON, IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
	wndClass.hIconSm =  LoadIcon(NULL, IDI_WINLOGO);//(HICON)LoadImage(GetModuleHandle(NULL), WINDOW_ICON, IMAGE_ICON, 32, 32, LR_LOADFROMFILE);

	// Register WNDCLASSEX
	dbg.Print("\tRegistering WNDCLASSEX...\n");
	if(!RegisterClassEx(&wndClass)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to register WNDCLASSEX!\n");
		return false;
	}

	// Adject Window Rect
	RECT wndRect = { 
		0, 0, static_cast<long>(wndSize.x), static_cast<long>(wndSize.y) 
	};
	AdjustWindowRectEx(&wndRect, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE, WS_EX_APPWINDOW);
	uint screenWidth = wndRect.right - wndRect.left;
	uint screenHeight = wndRect.bottom - wndRect.top;

	// Create Window
	dbg.Print("\tCreating Window...\n");
	wndHandle = CreateWindowEx(WS_EX_APPWINDOW, WINDOW_TITLE, WINDOW_TITLE, WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, screenWidth, screenHeight, NULL, NULL, *hInstance, NULL);
	if(!wndHandle){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to create window!\n");
		return false;
	}

	ShowWindow(wndHandle, SW_SHOW);
	ShowCursor(wndCursorVisible);

	dbg.Print("\tInitializing D3D11...\n");

	// Set Available Driver Types
	D3D_DRIVER_TYPE driverTypes[] = {
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_REFERENCE,
	};
	uint totalDriverTypes = ARRAYSIZE(driverTypes);

	// Set Available Feature Levels
	D3D_FEATURE_LEVEL featureLevels[] = {
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3
	};
	uint totalFeatureLevels = ARRAYSIZE(featureLevels);

	// Create Device
	HRESULT result;
	uint d3dCreationFlags = 0;
#ifdef _DEBUG
	d3dCreationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	dbg.Print("\tCreating Device and Context...\n");
	for(uint i = 0; i < totalDriverTypes; i++){
		result = D3D11CreateDevice(NULL, driverTypes[i], NULL, d3dCreationFlags, featureLevels, totalFeatureLevels, D3D11_SDK_VERSION, &d3dDevice, &d3dFeatureLevel, &d3dContext);
		if(SUCCEEDED(result)){
			d3dDriverType = driverTypes[i];
			break;
		}
	}
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to create D3D11 Device!\n");
		return false;
	}

	// Check For Requested MSAA Quality
	dbg.Print("\tChecking MSAA...\n");
	uint msaaQuality;
	if(d3dMSAASamples != 0){
		result = d3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &msaaQuality);
		if(FAILED(result)){
			dbg.PrintErr(DBG_ERROR_PRIORITY_LOW, "Failed to get requested MSAA quality!\n");
			d3dMSAASamples = 0;
		}
	}

	// Get DXGI Device
	IDXGIDevice* dxgiDevice;
	result = d3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get DXGI Device!\n");
		return false;
	}

	// Get DXGI Adapter
	IDXGIAdapter* dxgiAdapter;
	result = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get DXGI Adapter!\n");
		return false;
	}

	// Get DXGI Factory
	IDXGIFactory* dxgiFactory;
	result = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get DXGI Factory!\n");
		return false;
	}

	// Get DXGI Adapter Output
	IDXGIOutput* dxgiAdapterOutput;
	result = dxgiAdapter->EnumOutputs(NULL, &dxgiAdapterOutput);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get DXGI Adapter output!\n");
		return false;
	}

	// Get Ammount of Display Modes
	uint numModes;
	result = dxgiAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get ammount of display modes!\n");
		return false;
	}

	// Get Display Modes
	DXGI_MODE_DESC* dxgiDisplayModeList = new DXGI_MODE_DESC[numModes];
	result = dxgiAdapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, dxgiDisplayModeList);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get display modes!\n");
		return false;
	}

	uint vSyncNumerator, vSyncDenominator;

	for(uint i = 0; i < numModes; i++){
		if((dxgiDisplayModeList[i].Width == screenWidth) && (dxgiDisplayModeList[i].Height == screenHeight)){
			vSyncNumerator = dxgiDisplayModeList[i].RefreshRate.Numerator;
			vSyncDenominator = dxgiDisplayModeList[i].RefreshRate.Denominator;
		}
	}

	// Get Adapter Description
	DXGI_ADAPTER_DESC dxgiAdapterDesc;
	result = dxgiAdapter->GetDesc(&dxgiAdapterDesc);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get DXGI Adapter Description!\n");
		return false;
	}

	vRAM = static_cast<int>((dxgiAdapterDesc.DedicatedVideoMemory / 1024) / 1024); // GB

	int error;
	uint strlen;
	error = wcstombs_s(&strlen, vCard, 128, dxgiAdapterDesc.Description, 128);
	if(error != 0){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get Video Card Description!\n");
		return false;
	}

	// Set up SwapChain
	DXGI_SWAP_CHAIN_DESC dxgiSwapChainDesc;
	ZeroMemory(&dxgiSwapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
	dxgiSwapChainDesc.BufferCount = 2;
	dxgiSwapChainDesc.BufferDesc.Width = screenWidth;
	dxgiSwapChainDesc.BufferDesc.Height = screenHeight;
	if(wndFullscreen){
		dxgiSwapChainDesc.Windowed = false;
		dxgiSwapChainDesc.Flags = d3dCreationFlags | DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
	}else{
		dxgiSwapChainDesc.Windowed = true;
		dxgiSwapChainDesc.Flags = d3dCreationFlags;
	}
	dxgiSwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	dxgiSwapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
	dxgiSwapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
	dxgiSwapChainDesc.BufferDesc.RefreshRate.Numerator = vSyncNumerator;
	dxgiSwapChainDesc.BufferDesc.RefreshRate.Denominator = vSyncDenominator;
	dxgiSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	dxgiSwapChainDesc.OutputWindow = wndHandle;
	dxgiSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	if(d3dMSAASamples != 0){
		dxgiSwapChainDesc.SampleDesc.Count = d3dMSAASamples;
		dxgiSwapChainDesc.SampleDesc.Quality = msaaQuality - 1;
	}else{
		dxgiSwapChainDesc.SampleDesc.Count = 1;
		dxgiSwapChainDesc.SampleDesc.Quality = 0;
	}

	// Create SwapChain
	dbg.Print("\tCreating SwapChain...\n");
	result = dxgiFactory->CreateSwapChain(d3dDevice, &dxgiSwapChainDesc, &d3dSwapChain);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to create DXGI SwapChain!\n");
		return false;
	}

	// Disable Alt + Enter
	dxgiFactory->MakeWindowAssociation(wndHandle, DXGI_MWA_NO_ALT_ENTER);

	// Cleanup DXGI Tools used to create SwapChain and get Video Card details
	delete[] dxgiDisplayModeList;
	dxgiDisplayModeList = NULL;

	ReleaseCOM(dxgiAdapterOutput);
	ReleaseCOM(dxgiFactory);
	ReleaseCOM(dxgiAdapter);
	ReleaseCOM(dxgiDevice);

	// Set up Render Target
	ID3D11Texture2D* d3dBackBufferTex;
	result = d3dSwapChain->GetBuffer(NULL, __uuidof(ID3D11Texture2D), (LPVOID*)&d3dBackBufferTex);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to get Back Buffer!\n");
		return false;
	}

	dbg.Print("\tSetting up Render Target...\n");
	result = d3dDevice->CreateRenderTargetView(d3dBackBufferTex, NULL, &d3dBackBuffer);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to set up Render Target!\n");
		return false;
	}
	ReleaseCOM(d3dBackBufferTex);

	// Create Depth Stencil Buffer
	D3D11_TEXTURE2D_DESC d3dDepthStencilDesc;
	ZeroMemory(&d3dDepthStencilDesc, sizeof(D3D11_TEXTURE2D_DESC));
	d3dDepthStencilDesc.Width = screenWidth;
	d3dDepthStencilDesc.Height = screenHeight;
	d3dDepthStencilDesc.MipLevels = 1;
	d3dDepthStencilDesc.ArraySize = 1;
	d3dDepthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	if(d3dMSAASamples != 0){
		d3dDepthStencilDesc.SampleDesc.Count = d3dMSAASamples;
		d3dDepthStencilDesc.SampleDesc.Quality = msaaQuality - 1;
	}else{
		d3dDepthStencilDesc.SampleDesc.Count = 1;
		d3dDepthStencilDesc.SampleDesc.Quality = 0;
	}
	d3dDepthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
	d3dDepthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;

	result = d3dDevice->CreateTexture2D(&d3dDepthStencilDesc, NULL, &d3dDepthStencil);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to create Depth Stencil!\n");
		return false;
	}

	dbg.Print("\tCreating DepthStencilView...\n");
	result = d3dDevice->CreateDepthStencilView(d3dDepthStencil, NULL, &d3dDepthStencilView);
	if(FAILED(result)){
		dbg.PrintErr(DBG_ERROR_PRIORITY_SEVERE, "Failed to create Depth Stencil View!\n");
		return false;
	}

	d3dContext->OMSetRenderTargets(1, &d3dBackBuffer, d3dDepthStencilView);

	// Create Viewport
	dbg.Print("\tCreating Viewport...\n");
	d3dViewport.Width = static_cast<float>(screenWidth);
	d3dViewport.Height = static_cast<float>(screenHeight);
	d3dViewport.MinDepth = 0.0f;
	d3dViewport.MaxDepth = 1.0f;
	d3dViewport.TopLeftX = 0.0f;
	d3dViewport.TopLeftY = 0.0f;

	d3dContext->RSSetViewports(1, &d3dViewport);

	return true;
}

void Renderer::Shutdown(void)
{
	d3dSwapChain->SetFullscreenState(FALSE, NULL);

	if(d3dDepthStencilView){
		ReleaseCOM(d3dDepthStencilView);
	}

	if(d3dDepthStencil){
		ReleaseCOM(d3dDepthStencil);
	}

	if(d3dBackBuffer){
		ReleaseCOM(d3dBackBuffer);
	}

	if(d3dContext){
		ReleaseCOM(d3dContext);
	}

	if(d3dDevice){
		ReleaseCOM(d3dDevice);
	}

	if(d3dSwapChain){
		ReleaseCOM(d3dSwapChain);
	}

	ShowCursor(true);
	DestroyWindow(wndHandle);
	UnregisterClass("ACEngine-Window", *hInstance);

	hInstance = NULL;
}

void Renderer::SetClearColor(XMFLOAT4 clearColor)
{
	d3dClearColor[0] = clearColor.x;
	d3dClearColor[1] = clearColor.y;
	d3dClearColor[2] = clearColor.z;
	d3dClearColor[3] = clearColor.w;
}

void Renderer::ClearRenderTarget(void)
{
	if(wndStateChanged == 1){
		wndStateChanged = 0;
		d3dSwapChain->SetFullscreenState(TRUE, NULL);
		ShowWindow(wndHandle, SW_SHOW);
	}else if(wndStateChanged == 2){
		wndStateChanged = 0;
		ShowWindow(wndHandle, SW_SHOWMINIMIZED);
		d3dSwapChain->SetFullscreenState(FALSE, NULL);
	}

	if(d3dContext == NULL){
		return;
	}

	d3dContext->ClearRenderTargetView(d3dBackBuffer, d3dClearColor);
	d3dContext->ClearDepthStencilView(d3dDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, NULL);
}

void Renderer::Present(void)
{
	if(d3dSwapChain == NULL){
		return;
	}

	if(d3dVSync){
		d3dSwapChain->Present(1, NULL);
	}else{
		d3dSwapChain->Present(NULL, NULL);
	}
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hDC;

	switch(message){
		case WM_PAINT:
			hDC = BeginPaint(hWnd, &ps);
			EndPaint(hWnd, &ps);
			break;

		case WM_DESTROY:
			PostQuitMessage(0);
			break;

		case WM_SYSCOMMAND:
			switch(wParam){
				case SC_SCREENSAVE:
				case SC_MONITORPOWER:
				return 0;
			}
			break;

		case WM_KEYDOWN:
			if(wParam == VK_ESCAPE){
				PostQuitMessage(0);
			}
			break;

		case WM_SETFOCUS:
			if(wndState){
				wndStateChanged = 1;
			}
			break;
						 

		case WM_KILLFOCUS:
			if(wndState){
				wndStateChanged = 2;
			}
			break;

		case WM_MENUCHAR:
			MAKELRESULT(0, MNC_CLOSE);
			break;

	}
	return DefWindowProc(hWnd, message, wParam, lParam);
}

HWND Renderer::WndHandle(void)
{
	return wndHandle;
}